Security News
Supply Chain Attack Detected in Solana's web3.js Library
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
tslog is a TypeScript-first logging library for Node.js and browser applications. It provides a simple and flexible API for logging messages with various levels of severity, including support for structured logging, log rotation, and custom transports.
Basic Logging
Basic logging functionality allows you to log messages with different severity levels such as info, warn, and error.
const { Logger } = require('tslog');
const log = new Logger();
log.info('This is an info message');
log.warn('This is a warning message');
log.error('This is an error message');
Structured Logging
Structured logging allows you to log messages with additional context, making it easier to analyze logs.
const { Logger } = require('tslog');
const log = new Logger();
log.info('User data', { userId: 123, name: 'John Doe' });
Log Rotation
Log rotation helps manage log file sizes by rotating them based on size or time intervals.
const { Logger } = require('tslog');
const log = new Logger({ logFilePath: 'logs.log', logFileRotation: true });
log.info('This log will be rotated');
Custom Transports
Custom transports allow you to define how and where the logs should be sent or stored, providing flexibility in log management.
const { Logger } = require('tslog');
const customTransport = (logObject) => { console.log('Custom transport:', logObject); };
const log = new Logger({ type: 'json', customTransports: [customTransport] });
log.info('This will be handled by the custom transport');
Winston is a versatile logging library for Node.js that supports multiple transports, log levels, and formats. It is highly configurable and widely used in the Node.js community. Compared to tslog, Winston offers more built-in transports and a more extensive plugin ecosystem.
Pino is a fast and low-overhead logging library for Node.js. It focuses on performance and provides a simple API for logging. Pino is generally faster than tslog and is suitable for high-performance applications where logging overhead needs to be minimized.
Bunyan is a JSON logging library for Node.js that provides a simple and efficient way to log structured data. It includes features like log levels, serializers, and support for various transports. Bunyan is similar to tslog in its focus on structured logging but has a different API and feature set.
Powerful, fast and expressive logging for TypeScript and JavaScript
⚡ Fast and powerful
🪶 Lightweight and flexible
🏗 Universal: Works in Browsers and Node.js
👮️ Fully typed with TypeScript support (native source maps)
🗃 Pretty or JSON
output
📝 Customizable log level
⭕️ Supports circular structures
🦸 Custom pluggable loggers
💅 Object and error interpolation
🤓 Stack trace and pretty errors
👨👧👦 Sub-logger with inheritance
🙊 Mask/hide secrets and keys
📦 CJS & ESM with tree shaking support
✍️ Well documented and tested
import { Logger, ILogObj } from "tslog";
const log: Logger<ILogObj> = new Logger();
log.silly("I am a silly log.");
Donations help me allocate more time for my open source work.
npm install tslog
In order to run a native ES module in Node.js, you have to do two things:
"type": "module"
in package.json
.--experimental-specifier-resolution=node
Example package.json
{
"name": "NAME",
"version": "1.0.0",
"main": "index.js",
// here:
"type": "module",
"scripts": {
"build": "tsc -p .",
// and here:
"start": "node --enable-source-maps --experimental-specifier-resolution=node index.js"
},
"dependencies": {
"tslog": "^4"
},
"devDependencies": {
"typescript": "^4"
},
"engines": {
"node": ">=16"
}
}
With this package.json
you can simply build and run it:
npm run build
npm start
Otherwise:
ESM: Node.js with JavaScript:
node --enable-source-maps --experimental-specifier-resolution=node
CJS: Node.js with JavaScript:
node --enable-source-maps
ESM: Node.js with TypeScript and ts-node
:
node --enable-source-maps --experimental-specifier-resolution=node --no-warnings --loader ts-node/esm
CJS: Node.js with TypeScript and ts-node
:
node --enable-source-maps --no-warnings --loader ts-node/cjs
Browser:
<!doctype html>
<html lang="en">
<head>
<title>tslog example</title>
</head>
<body>
<h1>Example</h1>
<script src="tslog.js"></script>
<script>
const logger = new tslog.Logger();
logger.silly("I am a silly log.");
</script>
</body>
</html>
Enable TypeScript source map support:
This feature enables tslog
to reference a correct line number in your TypeScript source code.
// tsconfig.json
{
// ...
compilerOptions: {
// ...
"inlineSourceMap": true, // <!-- here
// we recommend using a current ES version
target: "es2020",
},
}
import { Logger } from "tslog";
const logger = new Logger({ name: "myLogger" });
logger.silly("I am a silly log.");
logger.trace("I am a trace log.");
logger.debug("I am a debug log.");
logger.info("I am an info log.");
logger.warn("I am a warn log with a json object:", { foo: "bar" });
logger.error("I am an error log.");
logger.fatal(new Error("I am a pretty Error with a stacktrace."));
silly
, trace
, debug
, info
, warn
, error
, fatal
(different colors)JSON
or suppressed outputminLevel
level can be set individually per transport
tslog >= v4
is a major rewrite and introduces breaking changes.
Please, follow this documentation when migrating.
Every incoming log message runs through a number of steps before being displayed or handed over to a "transport". Every step can be overwritten and adjusted.
BaseLogger.log()
methodlogObj
as well as the log property object) will be merged. If there are more than one, they will be put into properties called "0", "1", ... and so on. Alternatively, log message properties can be put into a property with a name configured with the argumentsArrayName
setting._meta
property or any other one configured with the setting metaProperty
._prettyFormatLogObjMeta
and the actual log payload will be formatted by prettyFormatLogObj
. Both steps can be overwritten with the settings formatMeta
and formatLogObj
.attachedTransports
. Last step is the actual transport, either JSON (transportJSON
), formatted (transportFormatted
) or omitted, if its set to "hidden". Both default transports can also be overwritten by the corresponding setting.By default, tslog
is optimized for the best developer experience and includes some default settings that may impact performance in production environments.
To ensure optimal performance in production, we recommend modifying these settings, such as hideLogPositionForProduction
(s. below), as needed.
tslog
comes with default log level 0: silly
, 1: trace
, 2: debug
, 3: info
, 4: warn
, 5: error
, 6: fatal
.
Tip: Each logging method has a return type, which is a JSON representation of the log message (
ILogObj
).
import { Logger } from "tslog";
const log = new Logger();
log.silly("I am a silly log.");
log.trace("I am a trace log.");
log.debug("I am a debug log.");
log.info("I am an info log.");
log.warn("I am a warn log with a json object:", { foo: "bar" });
log.error("I am an error log.");
log.fatal(new Error("I am a pretty Error with a stacktrace."));
In addition to the default log level, custom log level can be defined in the same way tslog
does it under the hood, by extending the BaseLogger
and utilizing the log
method.
log
method expects the following parameters:
Tip: Also the generic logging method (log()) returns a JSON representation of the log message (
ILogObject
).
import { BaseLogger, ILogObjMeta, ISettingsParam, ILogObj } from "./BaseLogger";
export class CustomLogger<LogObj> extends BaseLogger<LogObj> {
constructor(settings?: ISettingsParam<LogObj>, logObj?: LogObj) {
super(settings, logObj, 5);
}
/**
* Logs a _CUSTOM_ message.
* @param args - Multiple log attributes that should be logged.
* @return LogObject with meta property, when log level is >= minLevel
*/
public custom(...args: unknown[]): LogObj & ILogObjMeta | undefined {
return super.log(8, "CUSTOM", ...args);
}
}
Each tslog
-Logger instance can create sub-loggers and bequeath its settings to a child.
It is also possible to overwrite the LogObj
when creating a child.
Sub-loggers are a powerful feature when building a modular application and due to its inheritance make it easy to configure the entire application.
Use getSubLogger()
to create a child logger based on the current instance.
Example:
const mainLogger = new Logger({ type: "pretty", name: "MainLogger" });
mainLogger.silly("foo bar");
const firstSubLogger = mainLogger.getSubLogger({ name: "FirstSubLogger" });
firstSubLogger.silly("foo bar 1");
LogObj
You can also overwrite the LogObj
(s. below), when you create a sub-logger:
const mainLogObj = { main: true, sub: false };
const mainLogger = new Logger({ type: "pretty", name: "MainLogger" }, mainLogObj);
mainLogger.silly("foo bar");
const subLogObj = { main: false, sub: true };
const firstSubLogger = mainLogger.getSubLogger({ name: "FirstSubLogger" }, subLogObj);
firstSubLogger.silly("foo bar 1");
tslog
is highly customizable and pretty much every aspect can be either configured or overwritten.
A settings
object is the first parameter passed to the tslog
constructor:
const logger = new Logger<ILogObj>({ /* SETTINGS */ }, defaultLogObject);
settings
is a public property and can also be changed on runtime.
Example on changing minLevel
on runtime:
const logger = new Logger({
minLevel: 1
});
// visible
logger.log(1, "level_one", "LOG1");
// visible
logger.log(2, "level_two", "LOG2");
// change minLevel to 2
logger.settings.minLevel = 2;
// hidden
logger.log(1, "level_one", "LOG3");
// visible
logger.log(2, "level_two", "LOG4");
pretty
Default setting prints out a formatted structured "pretty" log entry.json
prints out a JSON
formatted log entry.hidden
suppresses any output whatsoever and can be used with attached loggers for example.Hint: Each JSON log is printed in one line, making it easily parsable by external services.
// pretty output
const defaultPrettyLogger = new Logger();
// also pretty output
const prettyLogger = new Logger({type: "pretty"});
// JSON output
const jsonLogger = new Logger({type: "json"});
// hidden output
const hiddenLogger = new Logger({type: "hidden"});
Each logger has an optional name.
You can find the name of the logger responsible for a log inside the Meta
-object or printed in pretty
mode.
Names get also inherited to sub-loggers and can be found inside the Meta
-object parentNames
as well as printed out with a separator (e.g. :
) in pretty
mode.
Simple name example:
new Logger({ name: "myLogger" });
Sub-loggers with an inherited name:
const mainLogger = new Logger({ type: "pretty", name: "MainLogger" });
mainLogger.silly("foo bar");
const firstSubLogger = mainLogger.getSubLogger({ name: "FirstSubLogger" });
firstSubLogger.silly("foo bar 1");
const secondSubLogger = firstSubLogger.getSubLogger({ name: "SecondSubLogger" });
secondSubLogger.silly("foo bar 2");
Output:
2022-11-17 10:45:47.705 SILLY [/examples/nodejs/index2.ts:51 MainLogger] foo bar
2022-11-17 10:45:47.706 SILLY [/examples/nodejs/index2.ts:54 MainLogger:FirstSubLogger ] foo bar 1
2022-11-17 10:45:47.706 SILLY [/examples/nodejs/index2.ts:57 MainLogger:FirstSubLogger:SecondSubLogger] foo bar 2
You can ignore every log message from being processed until a certain severity.
Default severities are:
0: silly
, 1: trace
, 2: debug
, 3: info
, 4: warn
, 5: error
, 6: fatal
const suppressSilly = new Logger({ minLevel: 1 });
suppressSilly.silly("Will be hidden");
suppressSilly.trace("Will be visible");
tslog
< 4 wrote all parameters into an arguments array. In tslog
>= 4 the main object becomes home for all log parameters, and they get merged with the default logObj
.
If you still want to put them into a separated parameter, you can do so by defining the argumentsArrayName
.
const logger = new Logger({
type: "json",
argumentsArrayName: "argumentsArray",
});
const logMsg = logger.silly("Test1", "Test2");
//logMsg : {
// argumentsArray: [ 'Test1', 'Test2' ],
// _meta: {
// [...]
// }
// }
// }
false
)By default, tslog
gathers and includes the log code position in the meta information of a logObj
o improve the developer experience.
However, this can significantly impact performance and slow down execution times in production.
To improve performance, you can disable this functionality by setting the option hideLogPositionForProduction
to true
.
Enables you to overwrite the looks of a formatted "pretty" log message by providing a template string. Following settings are available for styling:
Templates:
prettyLogTemplate
: template string for log messages. Possible placeholders:
{{yyyy}}
: year{{mm}}
: month{{dd}}
: day{{hh}}
: hour{{MM}}
: minute{{ss}}
: seconds{{ms}}
: milliseconds{{dateIsoStr}}
: Shortcut for {{yyyy}}.{{mm}}.{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}}
{{rawIsoStr}}
: Renders the date and time in ISO format (e.g.: YYYY-MM-DDTHH:mm:ss.SSSZ){{logLevelName}}
: name of the log level{{name}}
: optional name of the current logger and his parents (e.g. "ParentLogger:ThisLogger"){{nameWithDelimiterPrefix}}
: optional name of the current logger (s. above) with a delimiter in the beginning{{nameWithDelimiterSuffix}}
: optional name of the current logger (s. above) with a delimiter at the end{{fullFilePath}}
: a full path starting from /
root{{filePathWithLine}}
: a full path below the project path with line number{{fileNameWithLine}}
: file name with line numberprettyErrorTemplate
: template string for error message. Possible placeholders:
{{errorName}}
: name of the error{{errorMessage}}
: error message{{errorStack}}
: Placeholder for all stack lines defined by prettyErrorStackTemplate
prettyErrorStackTemplate
: template string for error stack trace lines. Possible placeholders:
{{fileName}}
: name of the file{{fileNameWithLine}}
: file name with line number{{filePathWithLine}}
: a full path below the project path with a line number{{method}}
: optional name of the invoking methodprettyErrorParentNamesSeparator
: separator to be used when joining names ot the parent logger, and the current one (default: :
)prettyErrorLoggerNameDelimiter
: if a logger name is set this delimiter will be added afterwardsprettyInspectOptions
: Available optionsIt's possible to add user defined tokes, by overwriting the addPlaceholders
in the settings.overwrite
. this callback allows to add or overwrite tokens in the placeholderValues
.
for example, to add the token: {{custom}}
;
const logger = new Logger({
type: "pretty",
prettyLogTemplate: "{{custom}} ",
overwrite: {
addPlaceholders: (logObjMeta: IMeta, placeholderValues: Record<string, string>) => {
placeholderValues["custom"] = "test";
},
},
});
this would yield in the token {{custom}}
being replaced with "test"
Styling:
stylePrettyLogs
: defines whether logs should be styled and colorizedprettyLogStyles
: provides colors and styles for different placeholders and can also be dependent on the value (e.g. log level)
white
), or an array of styles (e.g. ["bold", "white"]
), or a nested object with key being a value.{ SILLY: ["bold", "white"] }
which means: value "SILLY" should get a style of "bold" and "white". *
means any value other than the defined.prettyInspectOptions
: When a (potentially nested) object is printed out in Node.js, we use util.formatWithOptions
under the hood. With prettyInspectOptions
you can define the output. Possible valuesTime zone support:
prettyLogTimeZone
: Set timezone of pretty log messages to either UTC
(default) or local
(based on your server/browser configuration)tslog
collects meta information for every log, like runtime, code position etc. The meta information collected depends on the runtime (browser or Node.js) and is accessible through the LogObj
.
You can define the property containing this meta information with metaProperty
, which is "_meta" by default.
const logger = new Logger({
prettyLogTemplate: "{{yyyy}}.{{mm}}.{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}}\t{{logLevelName}}\t[{{filePathWithLine}}{{name}}]\t",
prettyErrorTemplate: "\n{{errorName}} {{errorMessage}}\nerror stack:\n{{errorStack}}",
prettyErrorStackTemplate: " • {{fileName}}\t{{method}}\n\t{{filePathWithLine}}",
prettyErrorParentNamesSeparator: ":",
prettyErrorLoggerNameDelimiter: "\t",
stylePrettyLogs: true,
prettyLogTimeZone: "UTC",
prettyLogStyles: {
logLevelName: {
"*": ["bold", "black", "bgWhiteBright", "dim"],
SILLY: ["bold", "white"],
TRACE: ["bold", "whiteBright"],
DEBUG: ["bold", "green"],
INFO: ["bold", "blue"],
WARN: ["bold", "yellow"],
ERROR: ["bold", "red"],
FATAL: ["bold", "redBright"],
},
dateIsoStr: "white",
filePathWithLine: "white",
name: ["white", "bold"],
nameWithDelimiterPrefix: ["white", "bold"],
nameWithDelimiterSuffix: ["white", "bold"],
errorName: ["bold", "bgRedBright", "whiteBright"],
fileName: ["yellow"],
fileNameWithLine: "white",
},
});
One of the most common ways of a password/secret breach is through log files.
Given the central position of tslog
as the collecting hub of all application logs, it's only natural to use it as a filter.
There are multiple ways of masking secrets, before they get exposed:
maskPlaceholder
: Placeholder to replaced masked secrets with, Default: [***]
maskValuesOfKeys
: Array of keys to replace the values with the placeholder (maskPlaceholder
). Default: ["password"]
maskValuesOfKeysCaseInsensitive
: Should the keys be matched case-insensitive (e.g. "password" would replace "password" as well as "Password", and "PASSWORD"). Default: false
maskValuesRegEx
: For even more flexibility, you can also replace strings and object values with a RegEx.Prefix every log message with an array of additional attributes.
Prefixes propagate to sub-loggers and can help to follow a chain of promises.
In addition to AsyncLocalStorage
, prefixes can help further distinguish different parts of a request.
Hint: A good example could be a GraphQL request, that by design could consist of multiple queries and/or mutations.
Example:
const logger = new Logger({
prefix: ["main-prefix", "parent-prefix"],
});
logger.info("MainLogger message");
// Output:
// main-prefix parent-prefix MainLogger message
const childLogger = logger.getSubLogger({
prefix: ["child1-prefix"],
});
childLogger.info("child1 message");
// Output:
// main-prefix parent-prefix child1-prefix MainLogger message
const grandchildLogger = childLogger.getSubLogger({
prefix: ["grandchild1-prefix"],
});
grandchildLogger.silly("grandchild1 message");
// Output:
// main-prefix parent-prefix child1-prefix grandchild1-prefix grandchild1 message
tslog
focuses on the one thing it does well: capturing logs.
Therefore, there is no built-in file system logging, log rotation, or similar.
Per default all logs go to console
, which can be overwritten (s. below).
However, you can easily attach as many transports as you wish, enabling you to do fancy stuff like sending messages to Slack or Telegram in case of an urgent error or forwarding them to a log aggregator service.
Attached transports are also inherited by sub-loggers.
Here is a very simple implementation used in our jest tests.
This example will suppress logs from being sent to console
(type: "hidden"
) and will instead collect them in an array
.
const transports: any[] = [];
const logger = new Logger({ type: "hidden" });
logger.attachTransport((logObj) => {
transports.push(logObj);
});
const logMsg = logger.info("Log message");
Here is an example of how to store all logs in a file.
import { Logger } from "tslog";
import { appendFileSync } from "fs";
const logger = new Logger();
logger.attachTransport((logObj) => {
appendFileSync("logs.txt", JSON.stringify(logObj) + "\n");
});
logger.debug("I am a debug log.");
logger.info("I am an info log.");
logger.warn("I am a warn log with a json object:", { foo: "bar" });
If you want to limit the file size of the stored logs, a good practice is to use file rotation, where old logs will be deleted automatically.
There is a great library called rotating-file-stream
solving this problem for us and even adding features like compression, file size limit etc.
npm i rotating-file-stream
tslog
:import { Logger } from "tslog";
import { createStream } from "rotating-file-stream";
const stream = createStream("tslog.log", {
size: "10M", // rotate every 10 MegaBytes written
interval: "1d", // rotate daily
compress: "gzip", // compress rotated files
});
const logger = new Logger();
logger.attachTransport((logObj) => {
stream.write(JSON.stringify(logObject) + "\n");
});
logger.debug("I am a debug log.");
logger.info("I am an info log.");
logger.warn("I am a warn log with a json object:", { foo: "bar" });
One of the key advantages of tslog
>= 4 is that you can overwrite pretty much every aspect of the log processing described in "Lifecycle of a log message".
For every log:
const logger = new Logger({
overwrite: {
mask: (args: unknown[]): unknown[] => {
// mask and return an array of log attributes for further processing
},
toLogObj: (args: unknown[], clonesLogObj?: LogObj): unknown => {
// convert the log attributes to a LogObj and return it
},
addMeta: (logObj: any, logLevelId: number, logLevelName: string) => {
// add meta information to the LogObj and return it
}
},
});
For pretty
logs:
const logger = new Logger({
type: "pretty",
overwrite: {
formatMeta: (meta?: IMeta) => {
// format LogObj meta object to a string and return it
},
formatLogObj: <LogObj>(maskedArgs: unknown[], settings: ISettings<LogObj>) => {
// format LogObj attributes to a string and return it
},
transportFormatted: (logMetaMarkup: string, logArgs: unknown[], logErrors: string[], settings: unknown) => {
// overwrite the default transport for formatted (e.g. pretty) log levels. e.g. replace console with StdOut, write to file etc.
},
},
});
For JSON
logs (no formatting happens here):
const logger = new Logger({
type: "json",
overwrite: {
transportJSON: (logObjWithMeta: any) => {
// transport the LogObj to console, StdOut, a file or an external service
},
},
});
logObj
As described in "Lifecycle of a log message", every log message goes through some lifecycle steps and becomes an object representation of the log with the name logObj
.
A default logObj can be passed to the tslog
constructor and will be cloned and merged into the log message. This makes tslog
>= 4 highly configurable and easy to integrate into any 3rd party service.
The entire logObj
will be printed out in JSON
mode and also returned by every log method.
Tip: All properties of the default
LogObj
containing function calls will be executed for every log message making use cases possible likerequestId
(s. below).
interface ILogObj {
foo: string;
}
const defaultLogObject: ILogObj = {
foo: "bar",
};
const logger = new Logger<ILogObj>({ type: "json" }, defaultLogObject);
const logMsg = logger.info("Test");
// logMsg: {
// '0': 'Test',
// foo: 'bar',
// _meta: {
// runtime: 'Nodejs',
// hostname: 'Eugenes-MBP.local',
// date: 2022-10-23T10:51:08.857Z,
// logLevelId: 3,
// logLevelName: 'INFO',
// path: {
// fullFilePath: 'file:///[...]/tslog/examples/nodejs/index.ts:113:23',
// fileName: 'index.ts',
// fileColumn: '23',
// fileLine: '113',
// filePath: '/examples/nodejs/index.ts',
// filePathWithLine: '/examples/nodejs/index.ts:113'
// }
// }
//}
tslog
follows a semantic release policy. A major version change indicates breaking changes.
tslog >=4
is less limiting when it comes to configuration. There are many use cases (especially when it comes to integration with 3rd party services) that now can be achieved elegantly and were not possible before.
tslog
Node.js 13.10 introduced a new feature called AsyncLocalStorage.
** Keep track of all subsequent calls and promises originated from a single request (e.g. HTTP).**
In a real world application a call to an API would lead to many logs produced across the entire application.
When debugging it can be quite handy to be able to group all logs based on a unique identifier, e.g. requestId
.
Some providers (e.g. Heroku
) already set a X-Request-ID
header, which we are going to use or fallback to a short ID generated by nanoid
.
In this example every subsequent logger is a sub-logger of the main logger and thus inherits all of its settings making requestId
available throughout the entire application without any further ado.
tslog
works with any API framework (like Express
, Koa
, Hapi
and so on), but in this example we are using Koa
.
import { AsyncLocalStorage } from "async_hooks";
import Koa from "koa";
import { customAlphabet } from "nanoid";
import { Logger } from "tslog";
interface ILogObj {
requestId?: string | (() => string | undefined);
}
const asyncLocalStorage: AsyncLocalStorage<{ requestId: string }> = new AsyncLocalStorage();
const defaultLogObject: ILogObj = {
requestId: () => asyncLocalStorage.getStore()?.requestId,
};
const logger = new Logger<ILogObj>({ type: "json" }, defaultLogObject);
export { logger };
logger.info("Test log without requestId");
const koaApp = new Koa();
/** START AsyncLocalStorage requestId middleware **/
koaApp.use(async (ctx: Koa.Context, next: Koa.Next) => {
// use x-request-id or fallback to a nanoid
const requestId: string = (ctx.request.headers["x-request-id"] as string) ?? customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 6)();
// every other Koa middleware will run within the AsyncLocalStorage context
await asyncLocalStorage.run({ requestId }, async () => {
return next();
});
});
/** END AsyncLocalStorage requestId middleware **/
// example middleware
koaApp.use(async (ctx: Koa.Context, next) => {
// log request
logger.silly({ originalUrl: ctx.originalUrl, status: ctx.response.status, message: ctx.response.message });
// also works with a sub-logger
const subLogger = logger.getSubLogger();
subLogger.info("Log containing requestId"); // <-- will contain a requestId
return await next();
});
koaApp.listen(3000);
logger.info("Server running on port 3000");
FAQs
Extensible TypeScript Logger for Node.js and Browser.
The npm package tslog receives a total of 108,942 weekly downloads. As such, tslog popularity was classified as popular.
We found that tslog demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.